using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using MacomberMap.Common.Components;
using MacomberMap.Common.Database_Interface;
using MacomberMap.Common.Internals;
using MacomberMap.Loader.User_Events;
using MacomberMap.Loader.User_Interfaces.Network_Map.Editor;
using MacomberMap.Common.User_Interfaces.Network_Map.Components;
namespace MacomberMap.Loader.User_Interfaces.Network_Map
{
    /// <summary>
    /// This class provides a map editor UI, including an editor-enabled network map, and a list view/property page of elements
    /// </summary>
    public partial class frmNetworkMapEditor : Form
    {
        #region Variable Declarations
        /// <summary>The last county the mouse was over</summary>
        private MM_Boundary LastCounty = null;

        /// <summary>The last element the mouse was over</summary>
        private MM_NetworkMap_Element LastElement = null;

        /// <summary>Our stack of undo actions</summary>
        private MM_UndoRedoStack<MM_Action> UndoStack = new MM_UndoRedoStack<MM_Action>(100);

        /// <summary>Our stack of redo actions</summary>
        private MM_UndoRedoStack<MM_Action> RedoStack = new MM_UndoRedoStack<MM_Action>(100);

        /// <summary>The currently selected element in the list view</summary>
        private MM_Editor_Element SelectedElement=null;

        /// <summary>Our editor components for elements</summary>
        private Dictionary<MM_Element, MM_Editor_Element> Elements = new Dictionary<MM_Element, MM_Editor_Element>();
        #endregion

        #region Variable interfaces
        /// <summary>Should we try and track underlying substations and lines?</summary>
        public bool TrackPostion { get { return btnTrackPosition.Checked; }  }
        #endregion


        #region Initialization
        /// <summary>
        /// Initialize a new network map
        /// </summary>
        public frmNetworkMapEditor()
        {
            InitializeComponent();

            try
            {
                lvComponents.Groups.Add("Mismatched Substations", "Mismatched Substations");
                lvComponents.Groups.Add("Mismatched Lines", "Mismatched Lines");
                lvComponents.Groups.Add("Substations", "Substations");
                lvComponents.Groups.Add("Lines", "Lines");
                lvComponents.Groups.Add("Counties", "Counties");
                nMap.ElementMouseDown += new MouseEventHandler(nMap_ElementMouseDown);
                nMap.ElementMouseMove += new MouseEventHandler(nMap_ElementMouseMove);
                nMap.MouseMove += new MouseEventHandler(nMap_MouseMove);
                nMap.DisplayOptions.CoordinatesPan += new EventHandler(DisplayMoved);
                nMap.DisplayOptions.CoordinatesZoomed += new EventHandler(DisplayMoved);
                InitializeDictionary();
                UpdateComponents();
            }
            catch (Exception)
            { }
        }

      

        

        /// <summary>
        /// Handle an element's movement
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void nMap_ElementMouseMove(object sender, MouseEventArgs e)
        {
            MM_NetworkMap_Substation MovingSubstation = sender as MM_NetworkMap_Substation;
            if (MovingSubstation != null)
                QueueUndoEvent(MovingSubstation.BaseStation, "LatLong", MovingSubstation.BaseStation.LatLong, MM_NetworkMap_Display.XYToLatLng(MovingSubstation.SubXY = new Point(nMap.DisplayOptions.TopLeftXY.X + e.X, nMap.DisplayOptions.TopLeftXY.Y + e.Y), nMap.DisplayOptions.ZoomLevel));                
        }

        /// <summary>
        /// Handle a mouse click on an element
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void nMap_ElementMouseDown(object sender, MouseEventArgs e)
        {
            MM_Element SentElem = sender as MM_Element;
            if (SentElem is MM_Substation)
                lblItem.Text = "Substation " + (SentElem as MM_Substation).LongName + " (" + SentElem.TEID.ToString("#,##0") + ")";
            else
                lblItem.Text = SentElem.ElemType.Name + " " + SentElem.Name + " (" + SentElem.TEID.ToString("#,##0") + ")";

            lvComponents.Select();
            lvComponents.SelectedIndices.Clear();                        
            Elements[SentElem].AssociatedItem.EnsureVisible();
            Elements[SentElem].AssociatedItem.Selected = true;
            propGrid.SelectedObject = Elements[SentElem];

        }
        /// <summary>
        /// Add our lines, substations and boundaries to the editor's dictionary
        /// </summary>
        private void InitializeDictionary()
        {
            foreach (MM_Substation Sub in MM_Repository.Substations.Values)
                Elements.Add(Sub, new MM_Editor_Substation(Sub));
            foreach (MM_Line Line in MM_Repository.Lines.Values)
                Elements.Add(Line, new MM_Editor_Element(Line));
            foreach (MM_Boundary Bound in MM_Repository.Counties.Values)
                Elements.Add(Bound, new MM_Editor_Boundary(Bound));
            Elements.Add(MM_Repository.StateBoundary, new MM_Editor_Boundary(MM_Repository.StateBoundary));
        }
        #endregion

        /// <summary>
        /// Update our map source
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateMapSource(object sender, EventArgs e)
        {
            nMap.SetMapSource(cmbRenderMode.Text);
        }

        /// <summary>
        /// Update our map transparency
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateMapTransparency(object sender, EventArgs e)
        {
            nMap.SetTransparency(cmbTransparency.Text);
        }

        private void btnOpen_Click(object sender, EventArgs e)
        {
            OpenFileDialog o = new OpenFileDialog();
            o.Title = "Open an MM Savecase or XML";
            o.Filter = "MM Information file (*.MM_Model;*.xml)|*.mm_model;*.xml";
            if (o.ShowDialog() == DialogResult.OK)
            {
                nMap.LoadXMLIntoRepository(o.FileName);
                UpdateComponents();
            }
            o.Dispose();
        }


        private void btnSave_Click(object sender, EventArgs e)
        {
            SaveFileDialog sD = new SaveFileDialog();
            sD.Title = "Save an MM Model Positioning";
            sD.Filter = "MM Information file (*.MM_Model;*.xml)|*.mm_model;*.xml";
            if (sD.ShowDialog() == DialogResult.OK)
                MM_Coordinate_Interface.SaveCoordinates(sD.FileName);
            sD.Dispose();

        }


        /// <summary>
        /// Update our list view with all components
        /// </summary>
        public void UpdateComponents()
        {
            //First make sure all our mismatches are contained in our collection
            foreach (MM_Element Elem in MM_Repository.MismatchedTEIDs.Values)
                if (Elem is MM_Substation && !Elements.ContainsKey(Elem))
                    Elements.Add(Elem, new MM_Editor_Substation((MM_Substation)Elem));
                else if (Elem is MM_Line && !Elements.ContainsKey(Elem))
                    Elements.Add(Elem, new MM_Editor_Line((MM_Line)Elem));

            //Now, prepare the list view, and add all items
            lvComponents.SuspendLayout();
            lvComponents.Items.Clear();
            foreach (MM_Editor_Element Elem in Elements.Values)
                Elem.AddComponent(lvComponents);        

            //Auto-adjust the columns, and reset the map.                             
            lvComponents.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
                    lvComponents.ResumeLayout(true);
            nMap.Invalidate();
        }

        //Handle a mouse single-click
        private void lvComponents_MouseClick(object sender, MouseEventArgs e)
        {
            ListViewHitTestInfo ht = lvComponents.HitTest(e.Location);
            if (ht.Item != null && ht.Item.Tag != null)
            {
                propGrid.SelectedObject = (SelectedElement = ht.Item.Tag as MM_Editor_Element);
                if (ht.Item.Tag is MM_Editor_Substation)
                    nMap.SetCenter((ht.Item.Tag as MM_Editor_Substation).LatLong);
                else if (ht.Item.Tag is MM_Editor_Line)
                {
                    PointF C1 = ((ht.Item.Tag as MM_Editor_Line).BaseElement as MM_Line).Substation1.LatLong;
                    PointF C2 = ((ht.Item.Tag as MM_Editor_Line).BaseElement as MM_Line).Substation2.LatLong;
                    nMap.SetCenter(new PointF((C1.X + C2.X) / 2f, (C1.Y + C2.Y) / 2f));
                }
                else if (ht.Item.Tag is MM_Editor_Boundary)
                nMap.SetCenter(((MM_Editor_Boundary)ht.Item.Tag).Centroid);

            }
            else
                propGrid.SelectedObject = SelectedElement = null;
        }

        /// <summary>
        /// Handle the mouse movement. If we've selected an object and are outside the bounds of the list view, start a drag event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvComponents_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && SelectedElement != null & !this.RectangleToScreen(lvComponents.Bounds).Contains(Cursor.Position))
                lvComponents.DoDragDrop(SelectedElement, DragDropEffects.Link);
                
        }

        //Handle a mouse double-click
        private void lvComponents_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            ListViewHitTestInfo ht = lvComponents.HitTest(e.Location);
            if (ht.Item != null && ht.Item.Tag is MM_Editor_Substation)
                nMap.SetCenter((ht.Item.Tag as MM_Editor_Substation).LatLong);
            else if (ht.Item != null && ht.Item.Tag is MM_Editor_Line)
            {
                PointF C1 = ((ht.Item.Tag as MM_Editor_Line).BaseElement as MM_Line).Substation1.LatLong;
                PointF C2 = ((ht.Item.Tag as MM_Editor_Line).BaseElement as MM_Line).Substation2.LatLong;
                nMap.SetCenter(new PointF((C1.X + C2.X) / 2f, (C1.Y + C2.Y) / 2f));
            }
        }

        /// <summary>
        /// Handle the zooming or panning of the display by adjusting the zoom level and coordinate boundaries
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DisplayMoved(object sender, EventArgs e)
        {
            lblZoom.Text = String.Format("Zoom: {0:00}", nMap.DisplayOptions.ZoomLevel);
        }

        /// <summary>
        /// Update the current mouse position
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void nMap_MouseMove(object sender, MouseEventArgs e)
        {
            lblZoom.Text = String.Format("Zoom: {0:00}", nMap.DisplayOptions.ZoomLevel);
            Point FullMouse = new Point(e.X + nMap.DisplayOptions.TopLeftXY.X, e.Y + nMap.DisplayOptions.TopLeftXY.Y);
            PointF LatLng = MM_NetworkMap_Display.XYToLatLng(FullMouse, nMap.DisplayOptions.ZoomLevel);
            lblMouse.Text = String.Format("Mouse: ({0:000},{1:000})", MousePosition.X, MousePosition.Y);
            lblLatLng.Text = String.Format("Location: ({0:000.000}, {1:000.000})", LatLng.X, LatLng.Y);            
            DetermineCounty(LatLng);            
        }

     

        /// <summary>
        /// Check the current mouse lat/lng against counties
        /// </summary>
        /// <param name="LatLng"></param>
        private void DetermineCounty(PointF LatLng)
        {
            if (!btnDrawCounties.Checked)
                return;
            if (LastCounty != null && LastCounty.HitTest(LatLng))
            {
                lblCounty.Text = "County: " + LastCounty.Name;
                return;
            }
            else
            {
                foreach (MM_Boundary Bound in MM_Repository.Counties.Values)
                    if (Bound.HitTest(LatLng))
                    {
                        lblCounty.Text = "County: " + (LastCounty = Bound).Name;
                        return;
                    }
            }
            lblCounty.Text = "";
        }

        /// <summary>
        /// Check the current mouse lat/lng against elements
        /// </summary>
        /// <param name="PixelXY">The coordinates to check</param>
        private void DetermineElement(Point PixelXY)
        {            
            if (LastElement != null && LastElement.HitTest(PixelXY))
            {
                if (LastElement is MM_NetworkMap_Substation)
                    lblItem.Text = "Substation: " + (LastElement as MM_NetworkMap_Substation).BaseStation.LongName;
                else if (LastElement is MM_NetworkMap_Line)
                    lblItem.Text = "Line: " + (LastElement as MM_NetworkMap_Line).BaseLine.Name + " (" + (LastElement as MM_NetworkMap_Line).BaseLine.Substation1.LongName + " to " + (LastElement as MM_NetworkMap_Line).BaseLine.Substation2.LongName + ")";
            }
            else
            {
                foreach (MM_NetworkMap_Substation Elem in nMap.VisibleSubs)
                    if (Elem.HitTest(PixelXY))
                    {
                        LastElement = Elem;
                        if (LastElement is MM_NetworkMap_Substation)
                            lblItem.Text = "Substation: " + (LastElement as MM_NetworkMap_Substation).BaseStation.LongName;
                        else if (LastElement is MM_NetworkMap_Line)
                            lblItem.Text = "Line: " + (LastElement as MM_NetworkMap_Line).BaseLine.Name + " (" + (LastElement as MM_NetworkMap_Line).BaseLine.Substation1.LongName + " to " + (LastElement as MM_NetworkMap_Line).BaseLine.Substation2.LongName + ")";
                        return;
                    }
                lblItem.Text = "";
            }
        }

        /// <summary>
        /// Update our network map by invalidating the display
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateNetworkMap(object sender, EventArgs e)
        {           
            nMap.Invalidate();
        }


        /// <summary>
        /// Highlight an element in the editor pane
        /// </summary>
        /// <param name="ElementToHighlight"></param>
        public void HighlightElement(MM_Element ElementToHighlight)
        {
            lvComponents.Focus();
            lvComponents.SelectedItems.Clear();
            MM_Editor_Element Elem = Elements[ElementToHighlight];
            Elem.AssociatedItem.Selected = true;
            Elem.AssociatedItem.Focused = true;
            //lvComponents.TopItem = ElementToHighlight.AssociatedItem;                        
            propGrid.SelectedObject = Elem;
            nMap.Focus();
        }


        /// <summary>
        /// Update an element's property by assigning it to the property grid.
        /// </summary>
        /// <param name="ElementToUpdate"></param>
        public void UpdateProperty(MM_Element ElementToUpdate)
        {
            if (ElementToUpdate != null)
                propGrid.SelectedObject = Elements[ElementToUpdate];
        }

        /// <summary>
        /// Update the new value by refreshing our network map.
        /// </summary>
        /// <param name="s"></param>
        /// <param name="e"></param>
        private void propGrid_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
        {
            this.Invalidate();
            QueueUndoEvent((propGrid.SelectedObject as MM_Editor_Element).BaseElement, e.ChangedItem.Label, e.OldValue, e.ChangedItem.Value);
        }



        #region Undo/redo queue
        /// <summary>
        /// Add or update an undo event into our queue
        /// </summary>
        /// <param name="ModifiedElement">The modified element</param>
        /// <param name="PropertyName">The modified property for that element</param>
        /// <param name="OldValue">The old value</param>
        /// <param name="NewValue">The new value</param>
        public void QueueUndoEvent(MM_Element ModifiedElement, String PropertyName, Object OldValue, Object NewValue)
        {
            if (UndoStack.Count > 0)
            {
                MM_Action LastAction = UndoStack.Peek();
                if (LastAction.ModifiedElement == ModifiedElement && LastAction.PropertyName == PropertyName)
                {
                    LastAction.AssignNewValue(NewValue);
                    return;
                }
            }
            UndoStack.Push(new MM_Action(ModifiedElement, PropertyName, OldValue, NewValue));

            if (PropertyName == "Add")
            {
                if (!Elements.ContainsKey(ModifiedElement))
                    if (ModifiedElement is MM_Substation)
                        Elements.Add(ModifiedElement, new MM_Editor_Substation((MM_Substation)ModifiedElement));
                    else if (ModifiedElement is MM_Line)
                        Elements.Add(ModifiedElement, new MM_Editor_Line((MM_Line)ModifiedElement));

                Elements[ModifiedElement].AddComponent(lvComponents);
                lvComponents.Sort();
            }

            else if (PropertyName == "Remove")
            {
                lvComponents.Items.Remove(Elements[ModifiedElement].AssociatedItem);
                Elements.Remove(ModifiedElement);
            }

            else if (PropertyName == "LatLong" && float.IsNaN(((PointF)OldValue).X) && !float.IsNaN(((PointF)NewValue).X))
            {
                MM_Repository.MismatchedTEIDs.Remove(ModifiedElement.TEID);
                MM_Repository.Substations.Add(ModifiedElement.Name, ModifiedElement as MM_Substation);
                MM_Repository.TEIDs.Add(ModifiedElement.TEID, ModifiedElement);
                Elements[ModifiedElement].AddComponent(lvComponents);                
            }
            btnUndo.Enabled = true;
            nMap.Invalidate();                
        }

      
        /// <summary>
        /// Undo a user event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnUndo_Click(object sender, EventArgs e)
        {
            if (UndoStack.Count > 0)
            {
                MM_Action Action = UndoStack.Pop();
                Action.AssignOldValue();
                RedoStack.Push(Action);
                btnRedo.Enabled = true;
                btnUndo.Enabled = UndoStack.Count > 0;
                nMap.Invalidate();

                if (Action.PropertyName == "Remove")
                {
                    if (!Elements.ContainsKey(Action.ModifiedElement))
                        if (Action.ModifiedElement is MM_Substation)
                            Elements.Add(Action.ModifiedElement, new MM_Editor_Substation((MM_Substation)Action.ModifiedElement));
                        else if (Action.ModifiedElement is MM_Line)
                            Elements.Add(Action.ModifiedElement, new MM_Editor_Line((MM_Line)Action.ModifiedElement));

                    Elements[Action.ModifiedElement].AddComponent(lvComponents);
                    lvComponents.Sort();
                }
                else if (Action.PropertyName == "Add")
                {
                    lvComponents.Items.Remove(Elements[Action.ModifiedElement].AssociatedItem);
                    if (Action.ModifiedElement is MM_Substation && MM_Repository.Substations.ContainsKey(Action.ModifiedElement.Name))
                        MM_Repository.Substations.Remove(Action.ModifiedElement.Name);
                    if (Action.ModifiedElement is MM_Line && MM_Repository.Lines.ContainsKey(Action.ModifiedElement.Name))
                        MM_Repository.Lines.Remove(Action.ModifiedElement.Name);
                    if (MM_Repository.TEIDs.ContainsKey(Action.ModifiedElement.TEID))
                        MM_Repository.TEIDs.Remove(Action.ModifiedElement.TEID);
                    if (MM_Repository.MismatchedTEIDs.ContainsKey(Action.ModifiedElement.TEID))
                        MM_Repository.TEIDs.Remove(Action.ModifiedElement.TEID);
                }

                else if (Action.PropertyName == "LatLong" && float.IsNaN(((PointF)Action.OldValue).X) && !float.IsNaN(((PointF)Action.NewValue).X))
                {
                    MM_Repository.MismatchedTEIDs.Add(Action.ModifiedElement.TEID, Action.ModifiedElement);
                    MM_Repository.Substations.Remove(Action.ModifiedElement.Name);
                    MM_Repository.TEIDs.Remove(Action.ModifiedElement.TEID);
                    Elements[Action.ModifiedElement].AddComponent(lvComponents);
                }
                nMap.Invalidate();
            }
        }

        /// <summary>
        /// Redo a user event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRedo_Click(object sender, EventArgs e)
        {
            if (RedoStack.Count > 0)
            {
                MM_Action Action = RedoStack.Pop();
                Action.AssignNewValue();
                UndoStack.Push(Action);
                btnUndo.Enabled = true;
                btnRedo.Enabled = RedoStack.Count > 0;



                if (Action.PropertyName == "Add")
                {
                    if (!Elements.ContainsKey(Action.ModifiedElement))
                        if (Action.ModifiedElement is MM_Substation)
                            Elements.Add(Action.ModifiedElement, new MM_Editor_Substation((MM_Substation)Action.ModifiedElement));
                        else if (Action.ModifiedElement is MM_Line)
                            Elements.Add(Action.ModifiedElement, new MM_Editor_Line((MM_Line)Action.ModifiedElement));

                    Elements[Action.ModifiedElement].AddComponent(lvComponents);
                    lvComponents.Sort();
                }
                else if (Action.PropertyName == "Remove")
                {
                    lvComponents.Items.Remove(Elements[Action.ModifiedElement].AssociatedItem);
                    if (Action.ModifiedElement is MM_Substation && MM_Repository.Substations.ContainsKey(Action.ModifiedElement.Name))
                        MM_Repository.Substations.Remove(Action.ModifiedElement.Name);
                    if (Action.ModifiedElement is MM_Line && MM_Repository.Lines.ContainsKey(Action.ModifiedElement.Name))
                        MM_Repository.Lines.Remove(Action.ModifiedElement.Name);
                    if (MM_Repository.TEIDs.ContainsKey(Action.ModifiedElement.TEID))
                        MM_Repository.TEIDs.Remove(Action.ModifiedElement.TEID);
                    if (MM_Repository.MismatchedTEIDs.ContainsKey(Action.ModifiedElement.TEID))
                        MM_Repository.TEIDs.Remove(Action.ModifiedElement.TEID);
                }

                else if (Action.PropertyName == "LatLong" && !float.IsNaN(((PointF)Action.OldValue).X) && float.IsNaN(((PointF)Action.NewValue).X))
                {
                    MM_Repository.MismatchedTEIDs.Remove(Action.ModifiedElement.TEID);
                    MM_Repository.Substations.Add(Action.ModifiedElement.Name, (MM_Substation)Action.ModifiedElement);
                    MM_Repository.TEIDs.Add(Action.ModifiedElement.TEID, Action.ModifiedElement);
                    Elements[Action.ModifiedElement].AddComponent(lvComponents);
                }
                nMap.Invalidate();
            }
        }

        #endregion

       

       

    }
}